#!/bin/ksh

set -e

MY_NAME=$(basename "$0")
MY_NAME="${MY_NAME:-kde-release-helper}"

usage() {
cat >&2 <<EOF
usage: $MY_NAME prepare new-KDE-version
       $MY_NAME check-lists
       $MY_NAME deps
       $MY_NAME merge
       $MY_NAME next
       $MY_NAME new port-name [commentary ...]
       $MY_NAME run
       $MY_NAME show {built|packaged|status|unpackaged} [all|l10n|sc]
       $MY_NAME update status

typical usecases:
  $MY_NAME prepare 4.9.3
  cd ../kde493
  # start hacking
  $MY_NAME check-lists
  $MY_NAME new kdepkg some cool KDE stuff
  $MY_NAME deps
  $MY_NAME run
  # after some progress but before committing
  $MY_NAME update status
  # when new KDE release is ready
  $MY_NAME merge
EOF
	exit 1
}

# echo-and-run
ear() {
	echo "$*" >&2
	"$@"
}

CLEANUP_ITEMS=
cleanup() {
	test -n "$CLEANUP_ITEMS" && ear rm -Rf $CLEANUP_ITEMS
}
trap cleanup EXIT

# Files to ignore during prepare and merge stages in addition to VCS ones
IGNORE_FILES="*.port.mk $MY_NAME STATUS kde4.pkgpath"

# File containing list of KDE packages in order they should be built
DEPS_LIST=kde.deps.list

# List of options for diff(1) to make it ignore files above
diff_ignore_opts() {
	for F in ${IGNORE_FILES}; do
		echo -n " -x $F"
	done
}

# Arg: KDE version in X.Y.Z format
# Output: kdeXYZ
kde_port_dir() {
	if ! echo "$1" | egrep -q '^[0-9]+\.[0-9]+\.[0-9]+$'; then
		echo "$1 is not a valid KDE version (X.Y.Z)" >&2
		exit 1
	fi
	echo "kde$1" | tr -d '.'
}

# List all ports (subdirectories) in current directory.
# Assume we're in KDE ports root directory.
# Output does not include l10n, see list_l10n_ports() below.
list_local_ports() {
	ls | while read P; do
		# Test for being directory for safety in case of symlinks
		test X"$P" != Xl10n -a \
		     X"$P" != Xtemplate -a \
		     -d "$P" \
		     -a -e "$P"/Makefile || continue
		echo "$P"
	done
}

# Assume we're in KDE ports root directory.
# Input: list of subdirectories.
# Output: list of distribution names.
port_dir_to_distname() {
	while read D; do
		(cd $D && make show=DISTNAME)
	done | sed -Ee 's@-([0-9]|\$).*@@' | sort
}

# Assume we're in KDE ports root directory.
list_l10n_ports() {
	test -d l10n || return 0
	for L in $(cd l10n/ru && make show=ALL_LANGS); do
		echo "kde-l10n-${L}"
	done | sort
}

# Assume we're in KDE ports root directory.
list_l10n_ports_dirs() {
	test -d l10n || return 0
	for L in $(cd l10n/ru && make show=ALL_LANGS); do
		echo "l10n/$L"
	done | sort
}

# Assume we're in the apporiate port's directory
list_remote_source() {
	local LIST URL
	local V=`make show=MODKDE4_VERSION`

	for URL in $(make MASTER_SITE_BACKUP= show=MASTER_SITES); do
		echo "$URL" | grep -q '^ftp://' || continue
		LIST="$(ear curl -sl $URL |
		    sed -e s@-${V}.*@@ |
		    grep -v '^\.' |
		    sort)" || continue
		test `echo -n "$LIST" | wc -l` -gt 1 || continue
		echo "$LIST"
		return 0
	done

	echo "Could not retrieve source files list." >&2
	echo "Probably more FTP servers with NLIST command supported" >&2
	echo "are needed in MASTER_SITES." >&2
	return 1
}

# Assume the same as list_remote_source().
# Input: sorted local list of ports.
# Output: list of new ports prefixed with "+"
compare_sources_lists() {
	T="`mktemp -t srccomp.XXXXXXXX`"
	OCLEANUP_ITEMS="$CLEANUP_ITEMS"
	CLEANUP_ITEMS="$CLEANUP_ITEMS $T"
	list_remote_source >"$T"
	test -s "$T" && diff -u - "$T" || true
	rm "$T"
	CLEANUP_ITEMS="$OCLEANUP_ITEMS"
}

# Assume we're in KDE ports root directory.
kde_version() {
	(cd libs && make show=MODKDE4_VERSION)
}

# Arguments: port, status
print_status_line() {
	printf '%-32s%s\n' "$1" "$2"
}

is_started() {
	test -d "`cd $1 && make show=WRKSRC`"
}

is_configured() {
	test -e "`cd $1 && make show=_CONFIGURE_COOKIE`"
}

is_built() {
	test -e "`cd $1 && make show=_BUILD_COOKIE`"
}

is_tested() {
	test -e "`cd $1 && make show=_TEST_COOKIE`"
}

is_packaged() {
	local P F FOUND FLAVORS=`cd $1 && make show=FLAVORS`
	for P in `cd $1 && make show=BUILD_PACKAGES`; do
		FOUND=
		for F in "" ${FLAVORS}; do
			if [ -e "`cd $1 && SUBPACKAGE=$P FLAVOR=$F make show=PKGFILE`" ]; then
				FOUND=Y
				break
			fi
		done
		test -n "$FOUND" || return 1
	done
	return 0
}

is_locked() {
	# LOCKDIR could be empty, see ports(7)
	LOCKDIR="${LOCKDIR:-X`cd $1 && make show=LOCKDIR`}"
	if [ "$LOCKDIR" != X ]; then
		test -e "${LOCKDIR#X}/`cd $1 && make show=FULLPKGNAME`.lock"
	else
		# XXX Better detection mechanizm?
		false
	fi
}

# Argument: KDE version in X.Y.Z format.
is_stable_ver() {
	# KDE alpha/beta/etc. versions start from 80
	test 80 -gt `echo $1 | sed -E 's/^.*\.([0-9]+)$/\1/'`
}

expand_port_list() {
	case ${1:-all} in
	all)
		list_local_ports
		list_l10n_ports_dirs
		;;
	sc)
		list_local_ports
		;;
	l10n)
		list_l10n_ports_dirs
		;;
	*)
		usage
		;;
	esac
}

### Main actions go here

prepare() {
	test $# -eq 1 || usage

	if ! [ -f ../kde4/kde4.port.mk -a X"`cd .. && basename $(pwd)`" = Xx11 ]; then
		echo "Please run $MY_NAME from KDE 4 directory in ports tree" >&2
		exit 2
	fi

	local NEW_KDE_PORT_VER=$1; shift
	local CUR_KDE_PORT_VER=`kde_version`

	cd ..
	local NEW_KDE_PORT_DIR=`kde_port_dir "$NEW_KDE_PORT_VER"` 
	if [ -e $NEW_KDE_PORT_DIR ]; then
		echo "x11/$NEW_KDE_PORT_DIR already exists" >&2
		exit 2
	fi

	echo '==> copying ports directory' >&2
	CLEANUP_ITEMS="$CLEANUP_ITEMS $NEW_KDE_PORT_DIR"
	cp -R "$OLDPWD" $NEW_KDE_PORT_DIR
	local LOCAL_PORTS=`cd $NEW_KDE_PORT_DIR && list_local_ports`

	echo '==> removing extra files' >&2
	for F in $IGNORE_FILES; do ear rm -f $NEW_KDE_PORT_DIR/$F; done
	find $NEW_KDE_PORT_DIR -name 'PLIST*.orig' -print0 | xargs -0rt rm
	if is_stable_ver $NEW_KDE_PORT_VER; then
		if is_stable_ver $CUR_KDE_PORT_VER; then
			# just mark distinfo missing
			ear rm -f $NEW_KDE_PORT_DIR/l10n/distinfo
		else
			# need to copy l10n back from some stable directory
			ear mkdir -p $NEW_KDE_PORT_DIR/l10n
			ear cp -R kde4/l10n/* $NEW_KDE_PORT_DIR/l10n
		fi
	else
		# Unstable KDE versions usually lacks l10n.
		# And if not, we don't care either, they will not
		# hit the CVS anyway.
		ear rm -Rf $NEW_KDE_PORT_DIR/l10n
	fi
	for P in $LOCAL_PORTS; do
		ear rm -f $NEW_KDE_PORT_DIR/$P/distinfo
	done

	echo '==> removing REVISION marks' >&2
	for P in $LOCAL_PORTS; do
		local OCLEANUP_ITEMS="$CLEANUP_ITEMS"
		CLEANUP_ITEMS="$CLEANUP_ITEMS $NEW_KDE_PORT_DIR/$P/Makefile.orig"
		perl -ni.orig -e '/^REVISION/ or print' $NEW_KDE_PORT_DIR/$P/Makefile
		cmp -s $NEW_KDE_PORT_DIR/$P/Makefile{.orig,} || echo $P
		rm $NEW_KDE_PORT_DIR/$P/Makefile.orig
		CLEANUP_ITEMS="$OCLEANUP_ITEMS"
        done

	echo '==> patching Makefile.inc' >&2
	local MI=$NEW_KDE_PORT_DIR/Makefile.inc
	local T=`mktemp -u "$MI".XXXXXXXXXXX`
	OCLEANUP_ITEMS="$CLEANUP_ITEMS"
	CLEANUP_ITEMS="$CLEANUP_ITEMS $T"
	local MSPATCH OSTABLE=stable NSTABLE=stable
	is_stable_ver $CUR_KDE_PORT_VER || OSTABLE=unstable
	is_stable_ver $NEW_KDE_PORT_VER || NSTABLE=unstable

	if [[ $OSTABLE = $NSTABLE ]]; then
		MSPATCH=cat
	else
		MSPATCH="sed /MASTER_SITES/s@${OSTABLE}/@${NSTABLE}/@"
	fi
	awk "
/^MODKDE4_FLAVOR[[:space:]]*:?=/ {
	print \"MODKDE4_FLAVOR =	${NEW_KDE_PORT_DIR}\";
	VL = 1;
}

{
	if (VL) {
		VL = 0;
	} else {
		print;
	}
}
	    " <"$MI" | $MSPATCH >"$T"

	if ! grep -q ^MODKDE4_FLAVOR "$T"; then
		# MODKDE4_FLAVOR could be missing
		echo "MODKDE4_FLAVOR =	$NEW_KDE_PORT_DIR" >>"$T"
	fi

	mv "$T" "$MI"

	if ! is_stable_ver $NEW_KDE_PORT_VER; then
		echo '==> patching Makefile' >&2
		sed -Ee '/^SUBDIR[[:space:]]*\+=[[:space:]]*l10n/d' \
		    <$NEW_KDE_PORT_DIR/Makefile >"$T"
		mv "$T" $NEW_KDE_PORT_DIR/Makefile
	fi
	CLEANUP_ITEMS="$OCLEANUP_ITEMS"

	set -x
	echo '==> adjusting the kde-shared-data port' >&2
	perl -ni.pre-$NEW_KDE_PORT_DIR -e "print; if (/^\\s*MASTER_SITES0/) {
		print \"DISTFILES +=	kde-runtime-${NEW_KDE_PORT_VER}.tar.xz:1\\n\";
		print \"MASTER_SITES1 =	\\\${MASTER_SITE_KDE:=${NSTABLE}/${NEW_KDE_PORT_VER}/src/}\\n\";
		}" kde-shared-data/Makefile
	set +x

	echo "==> creating STATUS" >&2
	for P in $LOCAL_PORTS `list_l10n_ports_dirs`; do
		print_status_line $P WAITING \
		    >>${NEW_KDE_PORT_DIR}/STATUS
	done

	# Avoid removing newly created directory
	CLEANUP_ITEMS=
}

check_lists() {
	test $# -eq 0 || usage
	echo '==> checking for new source packages' >&2
	list_local_ports |
	    port_dir_to_distname |
	    (cd libs && compare_sources_lists) |
	    egrep '^[\+-][^\+-]' |
	    grep -v '^+kde-l10n' || true

	test -d l10n || return 0
	echo '==> checking for l10n package list changes' >&2
	list_l10n_ports |
	    (cd l10n/ru && compare_sources_lists) |
	    egrep '^[\+-][^\+-]' || true
}

build_deps_list() {
	test $# -eq 0 || usage

	local KDEDIR=$(kde_port_dir `kde_version`)
	local P BDEPS DEP DEP2 T

	T=$(mktemp ${DEPS_LIST}.XXXXXXXX)
	OCLEANUP_ITEMS="$CLEANUP_ITEMS"
	CLEANUP_ITEMS="$CLEANUP_ITEMS $T"
	echo "===> Gathering lists of KDE ports packaged or being built" >&2
	for P in `list_local_ports; list_l10n_ports_dirs`; do
		# normalize build dependencies list by removing all but pkgpath
		BDEPS=$(cd $P && make show="BUILD_DEPENDS LIB_DEPENDS" |
		    sed -E -e 's@[^[:space:]]+:@@g' -e 's@[<>=].*@@')

		# take care of KDE SC dependencies only
		for DEP2 in $BDEPS; do
			echo "$DEP2"
		done |
		fgrep "x11/${KDEDIR}/" |
		sed -e "s%x11/${KDEDIR}/%%" -e 's%,.*%%' -e "s%\$% ${P}%"
	done | sort -u >"$T"
	mv -- "$T" "$DEPS_LIST"
	CLEANUP_ITEMS="$OCLEANUP_ITEMS"
}

choose_next() {
	test $# -eq 0 || usage

	local P
	tsort <"$DEPS_LIST" | while read P; do
		is_packaged $P && continue
		is_locked $P && continue

		if is_built $P; then
			echo "# $P was built"
			echo "(cd $P && make update-plist) && \\"
			echo "(cd $P && make port-lib-depends-check) && \\"
			echo "(cd $P && make package)"
		elif is_configured $P; then
			echo "# $P was configured"
			echo "(cd $P && make build)"
		elif is_started $P; then
			echo "# $P was started"
			echo "(cd $P && make configure)"
		else
			echo "# $P was not started"
			echo "(cd $P && make patch)"
		fi
		break
	done
}

new_port() {
	test $# -ge 1 || usage
	NEW_P=$1; shift
	mkdir "$NEW_P" "$NEW_P"/pkg
	sed -e "s@XYZ@${NEW_P}@g" \
	    -e "s@^COMMENT.*@COMMENT =	$*@" \
	    <template/Makefile \
	    >"$NEW_P"/Makefile
	echo "Now run:" >&2
	exec ${VISUAL:-${EDITOR:-vi}} $NEW_P/Makefile
}

merge_back() {
	test $# -eq 0 || usage

	# Avoid erroring out here, we'll print a nicer message below.
	local SKDEV=`cd libs && make show=_MODKDE4_STABLE || true`
	local KDEV=`cd libs && make show=MODKDE4_VERSION || true`

	if [ -e STATUS ] && egrep -v 'PACKAGED$' STATUS; then
		echo "Not all KDE SC $KDEV stuff looks like packaged" >&2
		exit 2
	fi

	if [ -f kde4.port.mk -o X"`cd .. && basename $(pwd)`" != Xx11 -o \
	     X"$KDEV" = X ]; then
		echo "Please run $MY_NAME from directory with KDE4 ports being tested" >&2
		exit 2
	fi

	local SKDED=$(kde_port_dir $SKDEV)
	local KDED=$(kde_port_dir $KDEV)
	if [ -e ../$SKDED ]; then
		echo "The x11/$SKDED directory already exists" >&2
		exit 2
	fi

	cd ..
	echo '==> patching Makefile.inc' >&2
	perl -ni -e 'm@^ *MODKDE4_FLAVOR@ or print' ${KDED}/Makefile.inc
	echo '==> moving directories' >&2
	mv kde4 $SKDED
	mv $KDED kde4
	echo '==> adjusting the kde-shared-data port' >&2
	perl -ni -e "
m@^ *DISTFILES\\s*\\+=\\s*kde-runtime-${KDED}\.@ or
m@^ *MASTER_SITES[1-9]\\s*=.*/${KDEV}/@ or
m@^ REVISION\\s*=@ or
print" kde-shared-data/Makefile
	(cd kde-shared-data && make makesum)

	echo '==> removing REVISIONs from meta/kde4' >&2
	local METAKDE=../meta/kde4
	test -d "$METAKDE" || METAKDE="../$METAKDE"
	test -d "$METAKDE" || METAKDE="../$METAKDE"
	test -d "$METAKDE" || {
		echo "===> meta/kde4 directory wasn't found" >&2
		return
	}
	perl -ni -e "/^ *REVISION/ or print" "$METAKDE/Makefile"
}

run_job() {
	test $# -eq 0 || usage
	for P in `list_local_ports`; do
		if is_packaged $P || is_locked $P; then
			continue
		fi

		echo "==> Working on port $P" >&2
		if is_built $P; then
			local TARGETS=
			if ! ls $P/pkg/PLIST*.orig >/dev/null 2>&1; then
				TARGETS=update-plist
			fi
			TARGETS="$TARGETS port-lib-depends-check package"
			TARGETS="$TARGETS lib-depends-check install clean"
			(cd $P && make $TARGETS)
		elif is_configured $P; then
			(cd $P && make build)
		elif is_started $P; then
			(cd $P && make configure)
		else
			(cd $P && make makesum prepare)
		fi
	done
}

# Assume to be running in KDE4 ports directory
show_status() {
	local P T V PORTLIST=$(expand_port_list "$1")

	V="`kde_version`"
	for P in $PORTLIST; do
		if is_packaged $P; then
			print_status_line $P PACKAGED
		elif is_built $P; then
			print_status_line $P BUILT
		elif is_configured $P; then
			print_status_line $P CONFIGURED
		elif is_started $P; then
			print_status_line $P STARTED
		else
			print_status_line $P WAITING
		fi
	done
}

show() {
	test $# -ge 1 -a $# -le 2 || usage

	if ! [ -f ../kde4/kde4.port.mk -a \
               X"`cd .. && basename $(pwd)`" = Xx11 ]; then
		echo "Please run $MY_NAME from a KDE 4 directory in ports tree" >&2
		exit 2
	fi

	local P WHAT PORTLIST=$(expand_port_list "$2")
	WHAT=$1; shift
	case $WHAT in
	c|co|con|conf|confi|config|configu|configur|configure|configured)
		for P in $PORTLIST; do
			is_configured $P && echo $P
		done
		;;

	b|bu|bui|buil|built)
		for P in $PORTLIST; do
			is_built $P && echo $P
		done
		;;

	p|pa|pac|pack|packa|packag|package|packaged)
		for P in $PORTLIST; do
			is_packaged $P && echo $P
		done
		;;

	s|st|sta|stat|statu|status)
		show_status "$@"
		;;

	u|un|unp|unpa|unpac|unpack|unpacka|unpackag|unpackage|unpackaged)
		for P in $PORTLIST; do
			is_packaged $P || echo $P
		done
		;;

	*)
		usage
	esac
	true
}

update_status() {
	test $# -eq 0 || usage

	local V=`kde_version`
	local T="`mktemp -u STATUS.XXXXXXXX`"
	local OCLEANUP_ITEMS="$CLEANUP_ITEMS"
	CLEANUP_ITEMS="$T $CLEANUP_ITEMS"
	if [ -e "STATUS" ]; then
		echo "===> Updating status of KDE $V porting"
		local P S
		show_status all | awk "
BEGIN {
	while (getline L <\"STATUS\") {
		split(L, PARTS);
		STATUS[PARTS[1]] = PARTS[2];
	}

	# From most to least useful, the latest one isn't actually needed now.
	STATUSES[1] = \"PACKAGED\";
	STATUSES[2] = \"BUILT\";
	STATUSES[3] = \"CONFIGURED\";
	STATUSES[4] = \"STARTED\";
	#STATUSES[5] = \"WAITING\";

	# We cannot use 'for(... in STATUSES)' because it doesn't restart
	# on every loop, but starts cycling items from the position where
	# previous loop was interrupted.
	NSTATUSES = 4
}

/^([[:space:]]*#.*)?\$/ {
	next;
}

{
	for (I = 1; I < NSTATUSES; I++) {
		S = STATUSES[I];
		if (STATUS[\$1] == S || \$2 == S) {
			printf(\"%-32s%s\\n\", \$1, S);
			next;
		}
	}
	printf(\"%-32s%s\\n\", \$1, \"WAITING\");
}
	    " | tee "$T"
	else
		echo "===> Old status file missing, recreating"
		show_status "$WHAT" | tee "$T"
	fi
	mv "$T" "STATUS"
	CLEANUP_ITEMS="$OCLEANUP_ITEMS"
}

update_l10n() {
	local d dirs l parargs
	if [[ $# -gt 0 && $1 == @([0-9]) ]]; then
		parargs="-j $1"
	fi
	if [ $# -eq 0 ]; then
		dirs=$(list_l10n_ports_dirs)
	else
		dirs=
		for l; do
			dirs="$dirs l10n/$l"
		done
	fi
	for d in $dirs; do
		echo SUBDIR=$d
	done | parallel $parargs make DANGEROUS=Yes update-plist
}

update() {
	test $# -ge 1 || usage

	if ! [ -f ../kde4/kde4.port.mk -a \
               X"`cd .. && basename $(pwd)`" = Xx11 ]; then
		echo "Please run $MY_NAME from a KDE 4 directory in ports tree" >&2
		exit 2
	fi

	local CMD=$1; shift
	case $CMD in
	l|l1|l10|l10n)
		update_l10n "$@"
		;;

	s|st|sta|stat|statu|status)
		update_status "$@"
		;;

	*)
		usage
		;;
	esac
}

# All actions take (at least) one argument
test $# -ge 1 || usage
ACTION=$1; shift

case $ACTION in
p|pr|pre|prep|prepa|prepar|prepare)
	prepare "$@"
	;;

check-l|check-li|check-lis|check-list|check-lists)
	check_lists "$@"
	;;

d|de|dep|deps)
	build_deps_list "$@"
	;;

m|me|mer|merg|merge)
	merge_back "$@"
	;;

nex|next)
	choose_next "$@"
	;;

new)
	new_port "$@"
	;;

r|ru|run)
	run_job "$@"
	;;

s|sh|sho|show)
	show "$@"
	;;

u|up|upd|upda|updat|update)
	update "$@"
	;;
esac
